package mylab.io.base;

import com.google.common.base.Preconditions;

import java.util.concurrent.TimeUnit;

import lombok.Data;
import mylab.base.foundation.lang.Callback;
import mylab.io.base.model.Msg;
import mylab.utils.common.CastUtil;
import mylab.utils.common.ExceptionUtil;


public interface MsgSender<M extends Msg> {

    /**
     * 异步发送
     */
    void asyncSend(M msg, Callback<Void> callback);

    /**
     * 同步发送【按默认超时】
     * 【关于同步发送】
     * 一般情形下，通讯是以 请求/应答 配对形式进行
     * 在NIO、特殊物理总线、或复杂上层业务通讯协议下，请求/应答 不一定是按时间顺序的
     * 通常，请求/应答 的配对机制应由 业务协议定义
     * 配对机制详见 {@code SyncPairity}
     *
     * @param msg 待发送的请求指令
     * @return 回应指令
     */
    default M syncSend(M msg) throws Exception {
        long timeout = getSyncTimeoutSeconds();
        return syncSend(msg, timeout, TimeUnit.SECONDS);
    }

    /**
     * 同步发送
     *
     * @param msg     待发送的请求指令
     * @param timeout 回应超时时间
     * @param unit    回应超时单位
     * @return 回应指令
     */
    M syncSend(M msg, long timeout, TimeUnit unit) throws Exception;

    /**
     * 同步发送
     *
     * @param msg      待发送的请求指令
     * @param timeout  回应超时时间
     * @param unit     回应超时单位
     * @param tryTimes 重试次数
     * @return 回应指令
     */
    default M syncSend(M msg, long timeout, TimeUnit unit, int tryTimes) throws Exception {
        Preconditions.checkState(timeout > 0, "timeout 必须大于0");
        Preconditions.checkState(tryTimes >= 0, "tryTimes 必须大于0");

        M response = null;
        Exception exception = null;
        for (int i = 0; i <= tryTimes; i++) {
            exception = null;

            try {
                response = syncSend(msg, timeout, unit);
                if (response != null) {
                    break;
                }
            } catch (Exception e) {
                exception = e;
            }
        }

        if (exception != null) {
            throw exception;
        }
        return response;
    }

    default int getSyncTimeoutSeconds() {
        return 5;
    }

    @Data
    public class SyncSendResult {
        Msg response;
        String cause;
        Throwable throwable;

        public <M extends Msg> M checkResult() {
            //有异常先抛异常
            if (throwable != null) {
                ExceptionUtil.throwException(throwable);
            }
            //失败抛运行时异常
            if (cause != null) {
                ExceptionUtil.throwException(cause);
            }

            return CastUtil.cast(response);
        }
    }
}
